// Copyright Envoy Gateway Authors
// SPDX-License-Identifier: Apache-2.0
// The full text of the Apache license is available in the LICENSE file at
// the root of the repo.

package ratelimit

import (
	"fmt"
	"os"
	"strconv"
	"testing"

	"github.com/stretchr/testify/require"
	appsv1 "k8s.io/api/apps/v1"
	autoscalingv2 "k8s.io/api/autoscaling/v2"
	corev1 "k8s.io/api/core/v1"
	policyv1 "k8s.io/api/policy/v1"
	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
	"k8s.io/apimachinery/pkg/api/resource"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/apimachinery/pkg/util/intstr"
	"k8s.io/utils/ptr"
	gwapiv1 "sigs.k8s.io/gateway-api/apis/v1"
	"sigs.k8s.io/yaml"

	egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1"
	"github.com/envoyproxy/gateway/internal/envoygateway/config"
	"github.com/envoyproxy/gateway/internal/utils/test"
)

const (
	// RedisAuthEnvVar is the redis auth.
	RedisAuthEnvVar = "REDIS_AUTH"
)

var ownerReferenceUID = map[string]types.UID{
	ResourceKindService:        "test-owner-reference-uid-for-service",
	ResourceKindDeployment:     "test-owner-reference-uid-for-deployment",
	ResourceKindServiceAccount: "test-owner-reference-uid-for-service-account",
}

func TestRateLimitLabelSelector(t *testing.T) {
	cases := []struct {
		name     string
		expected []string
	}{
		{
			name: "rateLimit-labelSelector",
			expected: []string{
				"app.kubernetes.io/name=envoy-ratelimit",
				"app.kubernetes.io/component=ratelimit",
				"app.kubernetes.io/managed-by=envoy-gateway",
			},
		},
	}

	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			got := LabelSelector()
			require.ElementsMatch(t, tc.expected, got)
		})
	}
}

func TestRateLimitLabels(t *testing.T) {
	cases := []struct {
		name     string
		expected map[string]string
	}{
		{
			name: "rateLimit-labels",
			expected: map[string]string{
				"app.kubernetes.io/name":       InfraName,
				"app.kubernetes.io/component":  "ratelimit",
				"app.kubernetes.io/managed-by": "envoy-gateway",
			},
		},
	}

	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			got := rateLimitLabels()
			require.Equal(t, tc.expected, got)
		})
	}
}

func TestServiceAccount(t *testing.T) {
	cfg, err := config.New(os.Stdout, os.Stderr)
	require.NoError(t, err)

	cfg.EnvoyGateway.RateLimit = &egv1a1.RateLimit{
		Backend: egv1a1.RateLimitDatabaseBackend{
			Type: egv1a1.RedisBackendType,
			Redis: &egv1a1.RateLimitRedisSettings{
				URL: "redis.redis.svc:6379",
			},
		},
	}
	r := NewResourceRender(cfg.ControllerNamespace, cfg.EnvoyGateway, ownerReferenceUID)

	sa, err := r.ServiceAccount()
	require.NoError(t, err)

	requireObject[corev1.ServiceAccount](t, sa, "testdata/envoy-ratelimit-serviceaccount.yaml")
}

func TestService(t *testing.T) {
	cfg, err := config.New(os.Stdout, os.Stderr)
	require.NoError(t, err)

	cases := []struct {
		caseName  string
		rateLimit *egv1a1.RateLimit
	}{
		{
			caseName: "default",
			rateLimit: &egv1a1.RateLimit{
				Backend: egv1a1.RateLimitDatabaseBackend{
					Type: egv1a1.RedisBackendType,
					Redis: &egv1a1.RateLimitRedisSettings{
						URL: "redis.redis.svc:6379",
					},
				},
			},
		},
		{
			caseName: "no-prometheus",
			rateLimit: &egv1a1.RateLimit{
				Backend: egv1a1.RateLimitDatabaseBackend{
					Type: egv1a1.RedisBackendType,
					Redis: &egv1a1.RateLimitRedisSettings{
						URL: "redis.redis.svc:6379",
					},
				},
				Telemetry: &egv1a1.RateLimitTelemetry{
					Metrics: &egv1a1.RateLimitMetrics{
						Prometheus: &egv1a1.RateLimitMetricsPrometheusProvider{
							Disable: true,
						},
					},
				},
			},
		},
	}

	for _, tc := range cases {
		cfg.EnvoyGateway.RateLimit = tc.rateLimit
		r := NewResourceRender(cfg.ControllerNamespace, cfg.EnvoyGateway, ownerReferenceUID)

		svc, err := r.Service()
		require.NoError(t, err)
		requireObject[corev1.Service](t, svc, fmt.Sprintf("testdata/services/envoy-ratelimit-service-%s.yaml", tc.caseName))
	}
}

func TestConfigmap(t *testing.T) {
	cfg, err := config.New(os.Stdout, os.Stderr)
	require.NoError(t, err)

	cfg.EnvoyGateway.RateLimit = &egv1a1.RateLimit{
		Backend: egv1a1.RateLimitDatabaseBackend{
			Type: egv1a1.RedisBackendType,
			Redis: &egv1a1.RateLimitRedisSettings{
				URL: "redis.redis.svc:6379",
			},
		},
	}
	r := NewResourceRender(cfg.ControllerNamespace, cfg.EnvoyGateway, ownerReferenceUID)
	cm, err := r.ConfigMap("")
	require.NoError(t, err)
	requireObject[corev1.ConfigMap](t, cm, "testdata/envoy-ratelimit-configmap.yaml")
}

func TestPDB(t *testing.T) {
	cfg, err := config.New(os.Stdout, os.Stderr)
	require.NoError(t, err)
	cases := []struct {
		caseName  string
		rateLimit *egv1a1.RateLimit
		pdb       *egv1a1.KubernetesPodDisruptionBudgetSpec
	}{
		{
			caseName: "default",
			rateLimit: &egv1a1.RateLimit{
				Backend: egv1a1.RateLimitDatabaseBackend{
					Type: egv1a1.RedisBackendType,
					Redis: &egv1a1.RateLimitRedisSettings{
						URL: "redis.redis.svc:6379",
					},
				},
			},
			pdb: &egv1a1.KubernetesPodDisruptionBudgetSpec{},
		},
		{
			caseName: "custom",
			rateLimit: &egv1a1.RateLimit{
				Backend: egv1a1.RateLimitDatabaseBackend{
					Type: egv1a1.RedisBackendType,
					Redis: &egv1a1.RateLimitRedisSettings{
						URL: "redis.redis.svc:6379",
					},
				},
			},
			pdb: &egv1a1.KubernetesPodDisruptionBudgetSpec{
				MinAvailable: &intstr.IntOrString{Type: intstr.Int, IntVal: 10},
				Patch: &egv1a1.KubernetesPatchSpec{
					Type: ptr.To(egv1a1.StrategicMerge),
					Value: apiextensionsv1.JSON{
						Raw: []byte("{\"spec\":{\"maxUnavailable\": 11}}"),
					},
				},
			},
		},
		{
			caseName: "with-name",
			rateLimit: &egv1a1.RateLimit{
				Backend: egv1a1.RateLimitDatabaseBackend{
					Type: egv1a1.RedisBackendType,
					Redis: &egv1a1.RateLimitRedisSettings{
						URL: "redis.redis.svc:6379",
					},
				},
			},
			pdb: &egv1a1.KubernetesPodDisruptionBudgetSpec{
				Name: ptr.To("custom-pdb-name"),
			},
		},
	}
	for _, tc := range cases {
		t.Run(tc.caseName, func(t *testing.T) {
			cfg.EnvoyGateway.RateLimit = tc.rateLimit

			cfg.EnvoyGateway.Provider = &egv1a1.EnvoyGatewayProvider{
				Type: egv1a1.ProviderTypeKubernetes,
				Kubernetes: &egv1a1.EnvoyGatewayKubernetesProvider{
					RateLimitPDB: tc.pdb,
				},
			}
			r := NewResourceRender(cfg.ControllerNamespace, cfg.EnvoyGateway, ownerReferenceUID)
			pdb, err := r.PodDisruptionBudget()
			require.NoError(t, err)

			requireObject[policyv1.PodDisruptionBudget](t, pdb, fmt.Sprintf("testdata/pdb/%s.yaml", tc.caseName))
		})
	}
}

func TestDeployment(t *testing.T) {
	cfg, err := config.New(os.Stdout, os.Stderr)
	require.NoError(t, err)
	rateLimit := &egv1a1.RateLimit{
		Backend: egv1a1.RateLimitDatabaseBackend{
			Type: egv1a1.RedisBackendType,
			Redis: &egv1a1.RateLimitRedisSettings{
				URL: "redis.redis.svc:6379",
			},
		},
	}
	cases := []struct {
		caseName  string
		rateLimit *egv1a1.RateLimit
		deploy    *egv1a1.KubernetesDeploymentSpec
	}{
		{
			caseName:  "default",
			rateLimit: rateLimit,
			deploy:    cfg.EnvoyGateway.GetEnvoyGatewayProvider().GetEnvoyGatewayKubeProvider().RateLimitDeployment,
		},
		{
			caseName: "disable-prometheus",
			rateLimit: &egv1a1.RateLimit{
				Backend: egv1a1.RateLimitDatabaseBackend{
					Type: egv1a1.RedisBackendType,
					Redis: &egv1a1.RateLimitRedisSettings{
						URL: "redis.redis.svc:6379",
					},
				},
				Telemetry: &egv1a1.RateLimitTelemetry{
					Metrics: &egv1a1.RateLimitMetrics{
						Prometheus: &egv1a1.RateLimitMetricsPrometheusProvider{
							Disable: true,
						},
					},
				},
			},
			deploy: cfg.EnvoyGateway.GetEnvoyGatewayProvider().GetEnvoyGatewayKubeProvider().RateLimitDeployment,
		},
		{
			caseName:  "patch-deployment",
			rateLimit: rateLimit,
			deploy: &egv1a1.KubernetesDeploymentSpec{
				Patch: &egv1a1.KubernetesPatchSpec{
					Type: ptr.To(egv1a1.StrategicMerge),
					Value: apiextensionsv1.JSON{
						Raw: []byte(`{"spec":{"template":{"spec":{"hostNetwork":true,"dnsPolicy":"ClusterFirstWithHostNet"}}}}`),
					},
				},
			},
		},
		{
			caseName:  "patch-deployment-containers",
			rateLimit: rateLimit,
			deploy: &egv1a1.KubernetesDeploymentSpec{
				Patch: &egv1a1.KubernetesPatchSpec{
					Type: ptr.To(egv1a1.StrategicMerge),
					Value: apiextensionsv1.JSON{
						Raw: []byte(`{
    "spec": {
        "template": {
            "spec": {
                "containers": [
                    {
                        "name": "envoy-ratelimit",
                        "imagePullPolicy": "Always",
                        "env": [
                            {
                                "name": "REDIS_TYPE",
                                "value": "sentinel"
                            }
                        ]
                    }
                ]
            }
        }
    }
}`),
					},
				},
			},
		},
		{
			caseName:  "custom",
			rateLimit: rateLimit,
			deploy: &egv1a1.KubernetesDeploymentSpec{
				Replicas: ptr.To[int32](2),
				Strategy: egv1a1.DefaultKubernetesDeploymentStrategy(),
				Pod: &egv1a1.KubernetesPodSpec{
					Annotations: map[string]string{
						"prometheus.io/scrape": "true",
					},
					SecurityContext: &corev1.PodSecurityContext{
						RunAsUser: ptr.To[int64](1000),
					},
				},
				Container: &egv1a1.KubernetesContainerSpec{
					Image: ptr.To("custom-image"),
					Resources: &corev1.ResourceRequirements{
						Limits: corev1.ResourceList{
							corev1.ResourceCPU:    resource.MustParse("400m"),
							corev1.ResourceMemory: resource.MustParse("2Gi"),
						},
						Requests: corev1.ResourceList{
							corev1.ResourceCPU:    resource.MustParse("200m"),
							corev1.ResourceMemory: resource.MustParse("1Gi"),
						},
					},
					SecurityContext: &corev1.SecurityContext{
						Privileged: ptr.To(true),
					},
				},
			},
		},
		{
			caseName:  "extension-env",
			rateLimit: rateLimit,
			deploy: &egv1a1.KubernetesDeploymentSpec{
				Replicas: ptr.To[int32](2),
				Strategy: egv1a1.DefaultKubernetesDeploymentStrategy(),
				Pod: &egv1a1.KubernetesPodSpec{
					Annotations: map[string]string{
						"prometheus.io/scrape": "true",
					},
					SecurityContext: &corev1.PodSecurityContext{
						RunAsUser: ptr.To[int64](1000),
					},
				},
				Container: &egv1a1.KubernetesContainerSpec{
					Env: []corev1.EnvVar{
						{
							Name:  "env_a",
							Value: "env_a_value",
						},
						{
							Name:  "env_b",
							Value: "env_b_value",
						},
					},
					Image: ptr.To("custom-image"),
					Resources: &corev1.ResourceRequirements{
						Limits: corev1.ResourceList{
							corev1.ResourceCPU:    resource.MustParse("400m"),
							corev1.ResourceMemory: resource.MustParse("2Gi"),
						},
						Requests: corev1.ResourceList{
							corev1.ResourceCPU:    resource.MustParse("200m"),
							corev1.ResourceMemory: resource.MustParse("1Gi"),
						},
					},
					SecurityContext: &corev1.SecurityContext{
						Privileged: ptr.To(true),
					},
				},
			},
		},
		{
			caseName:  "default-env",
			rateLimit: rateLimit,
			deploy: &egv1a1.KubernetesDeploymentSpec{
				Replicas: ptr.To[int32](2),
				Strategy: egv1a1.DefaultKubernetesDeploymentStrategy(),
				Pod: &egv1a1.KubernetesPodSpec{
					Annotations: map[string]string{
						"prometheus.io/scrape": "true",
					},
					SecurityContext: &corev1.PodSecurityContext{
						RunAsUser: ptr.To[int64](1000),
					},
				},
				Container: &egv1a1.KubernetesContainerSpec{
					Env:   nil,
					Image: ptr.To("custom-image"),
					Resources: &corev1.ResourceRequirements{
						Limits: corev1.ResourceList{
							corev1.ResourceCPU:    resource.MustParse("400m"),
							corev1.ResourceMemory: resource.MustParse("2Gi"),
						},
						Requests: corev1.ResourceList{
							corev1.ResourceCPU:    resource.MustParse("200m"),
							corev1.ResourceMemory: resource.MustParse("1Gi"),
						},
					},
					SecurityContext: &corev1.SecurityContext{
						Privileged: ptr.To(true),
					},
				},
			},
		},
		{
			caseName:  "override-env",
			rateLimit: rateLimit,
			deploy: &egv1a1.KubernetesDeploymentSpec{
				Replicas: ptr.To[int32](2),
				Strategy: egv1a1.DefaultKubernetesDeploymentStrategy(),
				Pod: &egv1a1.KubernetesPodSpec{
					Annotations: map[string]string{
						"prometheus.io/scrape": "true",
					},
					SecurityContext: &corev1.PodSecurityContext{
						RunAsUser: ptr.To[int64](1000),
					},
				},
				Container: &egv1a1.KubernetesContainerSpec{
					Env: []corev1.EnvVar{
						{
							Name:  UseStatsdEnvVar,
							Value: "true",
						},
					},
					Image: ptr.To("custom-image"),
					Resources: &corev1.ResourceRequirements{
						Limits: corev1.ResourceList{
							corev1.ResourceCPU:    resource.MustParse("400m"),
							corev1.ResourceMemory: resource.MustParse("2Gi"),
						},
						Requests: corev1.ResourceList{
							corev1.ResourceCPU:    resource.MustParse("200m"),
							corev1.ResourceMemory: resource.MustParse("1Gi"),
						},
					},
					SecurityContext: &corev1.SecurityContext{
						Privileged: ptr.To(true),
					},
				},
			},
		},
		{
			caseName: "redis-tls-settings",
			rateLimit: &egv1a1.RateLimit{
				Backend: egv1a1.RateLimitDatabaseBackend{
					Type: egv1a1.RedisBackendType,
					Redis: &egv1a1.RateLimitRedisSettings{
						URL: "redis.redis.svc:6379",
						TLS: &egv1a1.RedisTLSSettings{
							CertificateRef: &gwapiv1.SecretObjectReference{
								Name: "ratelimit-cert",
							},
						},
					},
				},
			},
			deploy: &egv1a1.KubernetesDeploymentSpec{
				Replicas: ptr.To[int32](2),
				Strategy: egv1a1.DefaultKubernetesDeploymentStrategy(),
				Pod: &egv1a1.KubernetesPodSpec{
					Annotations: map[string]string{
						"prometheus.io/scrape": "true",
					},
					SecurityContext: &corev1.PodSecurityContext{
						RunAsUser: ptr.To[int64](1000),
					},
				},
				Container: &egv1a1.KubernetesContainerSpec{
					Env: []corev1.EnvVar{
						{
							Name:  RedisAuthEnvVar,
							Value: "redis_auth_password",
						},
						{
							Name:  UseStatsdEnvVar,
							Value: "true",
						},
					},
					Image: ptr.To("custom-image"),
					Resources: &corev1.ResourceRequirements{
						Limits: corev1.ResourceList{
							corev1.ResourceCPU:    resource.MustParse("400m"),
							corev1.ResourceMemory: resource.MustParse("2Gi"),
						},
						Requests: corev1.ResourceList{
							corev1.ResourceCPU:    resource.MustParse("200m"),
							corev1.ResourceMemory: resource.MustParse("1Gi"),
						},
					},
					SecurityContext: &corev1.SecurityContext{
						Privileged: ptr.To(true),
					},
				},
			},
		},
		{
			caseName: "tolerations",
			rateLimit: &egv1a1.RateLimit{
				Backend: egv1a1.RateLimitDatabaseBackend{
					Type: egv1a1.RedisBackendType,
					Redis: &egv1a1.RateLimitRedisSettings{
						URL: "redis.redis.svc:6379",
						TLS: &egv1a1.RedisTLSSettings{
							CertificateRef: &gwapiv1.SecretObjectReference{
								Name: "ratelimit-cert",
							},
						},
					},
				},
			},
			deploy: &egv1a1.KubernetesDeploymentSpec{
				Replicas: ptr.To[int32](2),
				Strategy: egv1a1.DefaultKubernetesDeploymentStrategy(),
				Pod: &egv1a1.KubernetesPodSpec{
					Annotations: map[string]string{
						"prometheus.io/scrape": "true",
					},
					SecurityContext: &corev1.PodSecurityContext{
						RunAsUser: ptr.To[int64](1000),
					},
					Tolerations: []corev1.Toleration{
						{
							Key:      "node-type",
							Operator: corev1.TolerationOpExists,
							Effect:   corev1.TaintEffectNoSchedule,
							Value:    "router",
						},
					},
				},
				Container: &egv1a1.KubernetesContainerSpec{
					Env: []corev1.EnvVar{
						{
							Name:  RedisAuthEnvVar,
							Value: "redis_auth_password",
						},
						{
							Name:  UseStatsdEnvVar,
							Value: "true",
						},
					},
					Image: ptr.To("custom-image"),
					Resources: &corev1.ResourceRequirements{
						Limits: corev1.ResourceList{
							corev1.ResourceCPU:    resource.MustParse("400m"),
							corev1.ResourceMemory: resource.MustParse("2Gi"),
						},
						Requests: corev1.ResourceList{
							corev1.ResourceCPU:    resource.MustParse("200m"),
							corev1.ResourceMemory: resource.MustParse("1Gi"),
						},
					},
					SecurityContext: &corev1.SecurityContext{
						Privileged: ptr.To(true),
					},
				},
			},
		},
		{
			caseName: "volumes",
			rateLimit: &egv1a1.RateLimit{
				Backend: egv1a1.RateLimitDatabaseBackend{
					Type: egv1a1.RedisBackendType,
					Redis: &egv1a1.RateLimitRedisSettings{
						URL: "redis.redis.svc:6379",
						TLS: &egv1a1.RedisTLSSettings{
							CertificateRef: &gwapiv1.SecretObjectReference{
								Name: "ratelimit-cert-origin",
							},
						},
					},
				},
			},
			deploy: &egv1a1.KubernetesDeploymentSpec{
				Replicas: ptr.To[int32](2),
				Strategy: egv1a1.DefaultKubernetesDeploymentStrategy(),
				Pod: &egv1a1.KubernetesPodSpec{
					Annotations: map[string]string{
						"prometheus.io/scrape": "true",
					},
					SecurityContext: &corev1.PodSecurityContext{
						RunAsUser: ptr.To[int64](1000),
					},
					Tolerations: []corev1.Toleration{
						{
							Key:      "node-type",
							Operator: corev1.TolerationOpExists,
							Effect:   corev1.TaintEffectNoSchedule,
							Value:    "router",
						},
					},
					Volumes: []corev1.Volume{
						{
							Name: "certs",
							VolumeSource: corev1.VolumeSource{
								Secret: &corev1.SecretVolumeSource{
									SecretName:  "custom-cert",
									DefaultMode: ptr.To[int32](420),
								},
							},
						},
					},
				},
				Container: &egv1a1.KubernetesContainerSpec{
					Env: []corev1.EnvVar{
						{
							Name:  RedisAuthEnvVar,
							Value: "redis_auth_password",
						},
						{
							Name:  UseStatsdEnvVar,
							Value: "true",
						},
					},
					Image: ptr.To("custom-image"),
					Resources: &corev1.ResourceRequirements{
						Limits: corev1.ResourceList{
							corev1.ResourceCPU:    resource.MustParse("400m"),
							corev1.ResourceMemory: resource.MustParse("2Gi"),
						},
						Requests: corev1.ResourceList{
							corev1.ResourceCPU:    resource.MustParse("200m"),
							corev1.ResourceMemory: resource.MustParse("1Gi"),
						},
					},
					SecurityContext: &corev1.SecurityContext{
						Privileged: ptr.To(true),
					},
					VolumeMounts: []corev1.VolumeMount{},
				},
			},
		},
		{
			caseName:  "with-node-selector",
			rateLimit: rateLimit,
			deploy: &egv1a1.KubernetesDeploymentSpec{
				Pod: &egv1a1.KubernetesPodSpec{
					NodeSelector: map[string]string{
						"key1": "value1",
						"key2": "value2",
					},
				},
			},
		},
		{
			caseName:  "with-topology-spread-constraints",
			rateLimit: rateLimit,
			deploy: &egv1a1.KubernetesDeploymentSpec{
				Pod: &egv1a1.KubernetesPodSpec{
					TopologySpreadConstraints: []corev1.TopologySpreadConstraint{
						{
							MaxSkew:           1,
							TopologyKey:       "kubernetes.io/hostname",
							WhenUnsatisfiable: corev1.DoNotSchedule,
							LabelSelector: &metav1.LabelSelector{
								MatchLabels: map[string]string{"app": "foo"},
							},
							MatchLabelKeys: []string{"pod-template-hash"},
						},
					},
				},
			},
		},
		{
			caseName: "enable-tracing",
			rateLimit: &egv1a1.RateLimit{
				Backend: egv1a1.RateLimitDatabaseBackend{
					Type: egv1a1.RedisBackendType,
					Redis: &egv1a1.RateLimitRedisSettings{
						URL: "redis.redis.svc:6379",
					},
				},
				Telemetry: &egv1a1.RateLimitTelemetry{
					Tracing: &egv1a1.RateLimitTracing{
						Provider: &egv1a1.RateLimitTracingProvider{
							URL: "http://trace-collector.envoy-gateway-system.svc.cluster.local:4318",
						},
					},
				},
			},
		},
		{
			caseName: "enable-tracing-custom",
			rateLimit: &egv1a1.RateLimit{
				Backend: egv1a1.RateLimitDatabaseBackend{
					Type: egv1a1.RedisBackendType,
					Redis: &egv1a1.RateLimitRedisSettings{
						URL: "redis.redis.svc:6379",
					},
				},
				Telemetry: &egv1a1.RateLimitTelemetry{
					Tracing: &egv1a1.RateLimitTracing{
						SamplingRate: ptr.To[uint32](55),
						Provider: &egv1a1.RateLimitTracingProvider{
							URL: "trace-collector.envoy-gateway-system.svc.cluster.local:4317",
						},
					},
				},
			},
		},
		{
			caseName: "merge-labels",
			rateLimit: &egv1a1.RateLimit{
				Backend: egv1a1.RateLimitDatabaseBackend{
					Type: egv1a1.RedisBackendType,
					Redis: &egv1a1.RateLimitRedisSettings{
						URL: "redis.redis.svc:6379",
					},
				},
			},
			deploy: &egv1a1.KubernetesDeploymentSpec{
				Pod: &egv1a1.KubernetesPodSpec{
					Labels: map[string]string{
						"app.kubernetes.io/name":       InfraName,
						"app.kubernetes.io/component":  "ratelimit",
						"app.kubernetes.io/managed-by": "envoy-gateway",
						"key1":                         "value1",
						"key2":                         "value2",
					},
				},
			},
		},
		{
			caseName: "merge-annotations",
			rateLimit: &egv1a1.RateLimit{
				Backend: egv1a1.RateLimitDatabaseBackend{
					Type: egv1a1.RedisBackendType,
					Redis: &egv1a1.RateLimitRedisSettings{
						URL: "redis.redis.svc:6379",
					},
				},
			},
			deploy: &egv1a1.KubernetesDeploymentSpec{
				Pod: &egv1a1.KubernetesPodSpec{
					Annotations: map[string]string{
						"prometheus.io/path":   "/metrics",
						"prometheus.io/port":   strconv.Itoa(PrometheusPort),
						"prometheus.io/scrape": "true",
						"key1":                 "value1",
						"key2":                 "value2",
					},
				},
			},
		},
	}
	for _, tc := range cases {
		t.Run(tc.caseName, func(t *testing.T) {
			cfg.EnvoyGateway.RateLimit = tc.rateLimit

			cfg.EnvoyGateway.Provider = &egv1a1.EnvoyGatewayProvider{
				Type: egv1a1.ProviderTypeKubernetes,
				Kubernetes: &egv1a1.EnvoyGatewayKubernetesProvider{
					RateLimitDeployment: tc.deploy,
				},
			}
			r := NewResourceRender(cfg.ControllerNamespace, cfg.EnvoyGateway, ownerReferenceUID)
			dp, err := r.Deployment()
			require.NoError(t, err)
			requireObject[appsv1.Deployment](t, dp, fmt.Sprintf("testdata/deployments/%s.yaml", tc.caseName))
		})
	}
}

func TestHorizontalPodAutoscaler(t *testing.T) {
	cfg, err := config.New(os.Stdout, os.Stderr)
	require.NoError(t, err)
	cases := []struct {
		caseName            string
		rateLimit           *egv1a1.RateLimit
		rateLimitHpa        *egv1a1.KubernetesHorizontalPodAutoscalerSpec
		rateLimitDeployment *egv1a1.KubernetesDeploymentSpec
	}{
		{
			caseName: "default",
			rateLimit: &egv1a1.RateLimit{
				Backend: egv1a1.RateLimitDatabaseBackend{
					Type: egv1a1.RedisBackendType,
					Redis: &egv1a1.RateLimitRedisSettings{
						URL: "redis.redis.svc:6379",
					},
				},
			},
			rateLimitHpa: &egv1a1.KubernetesHorizontalPodAutoscalerSpec{},
		},
		{
			caseName: "custom",
			rateLimit: &egv1a1.RateLimit{
				Backend: egv1a1.RateLimitDatabaseBackend{
					Type: egv1a1.RedisBackendType,
					Redis: &egv1a1.RateLimitRedisSettings{
						URL: "redis.redis.svc:6379",
					},
				},
			},
			rateLimitHpa: &egv1a1.KubernetesHorizontalPodAutoscalerSpec{
				MinReplicas: ptr.To[int32](5),
				MaxReplicas: ptr.To[int32](10),
				Metrics: []autoscalingv2.MetricSpec{
					{
						Resource: &autoscalingv2.ResourceMetricSource{
							Name: corev1.ResourceCPU,
							Target: autoscalingv2.MetricTarget{
								Type:               autoscalingv2.UtilizationMetricType,
								AverageUtilization: ptr.To[int32](60),
							},
						},
						Type: autoscalingv2.ResourceMetricSourceType,
					},
					{
						Resource: &autoscalingv2.ResourceMetricSource{
							Name: corev1.ResourceMemory,
							Target: autoscalingv2.MetricTarget{
								Type:               autoscalingv2.UtilizationMetricType,
								AverageUtilization: ptr.To[int32](70),
							},
						},
						Type: autoscalingv2.ResourceMetricSourceType,
					},
				},
			},
		},
		{
			caseName: "with-deployment-name",
			rateLimit: &egv1a1.RateLimit{
				Backend: egv1a1.RateLimitDatabaseBackend{
					Type: egv1a1.RedisBackendType,
					Redis: &egv1a1.RateLimitRedisSettings{
						URL: "redis.redis.svc:6379",
					},
				},
			},
			rateLimitHpa: &egv1a1.KubernetesHorizontalPodAutoscalerSpec{},
			rateLimitDeployment: &egv1a1.KubernetesDeploymentSpec{
				Name: ptr.To("foo"),
			},
		},
		{
			caseName: "with-name",
			rateLimit: &egv1a1.RateLimit{
				Backend: egv1a1.RateLimitDatabaseBackend{
					Type: egv1a1.RedisBackendType,
					Redis: &egv1a1.RateLimitRedisSettings{
						URL: "redis.redis.svc:6379",
					},
				},
			},
			rateLimitHpa: &egv1a1.KubernetesHorizontalPodAutoscalerSpec{
				Name: ptr.To("custom-hpa-name"),
			},
		},
	}
	for _, tc := range cases {
		t.Run(tc.caseName, func(t *testing.T) {
			cfg.EnvoyGateway.RateLimit = tc.rateLimit

			cfg.EnvoyGateway.Provider = &egv1a1.EnvoyGatewayProvider{
				Type: egv1a1.ProviderTypeKubernetes,
				Kubernetes: &egv1a1.EnvoyGatewayKubernetesProvider{
					RateLimitHpa:        tc.rateLimitHpa,
					RateLimitDeployment: tc.rateLimitDeployment,
				},
			}
			r := NewResourceRender(cfg.ControllerNamespace, cfg.EnvoyGateway, ownerReferenceUID)
			hpa, err := r.HorizontalPodAutoscaler()
			require.NoError(t, err)

			requireObject[autoscalingv2.HorizontalPodAutoscaler](t, hpa, fmt.Sprintf("testdata/hpa/%s.yaml", tc.caseName))
		})
	}
}

func loadObject[T any](filename string) (*T, error) {
	obj := new(T)
	data, err := os.ReadFile(filename)
	if err != nil {
		return obj, err
	}

	_ = yaml.Unmarshal(data, obj)
	return obj, nil
}

func requireObject[ObjectT any](t *testing.T, got *ObjectT, filename string) {
	if test.OverrideTestData() {
		cmYAML, err := yaml.Marshal(got)
		require.NoError(t, err)
		// nolint:gosec
		err = os.WriteFile(filename, cmYAML, 0o644)
		require.NoError(t, err)
		return
	}

	expected, err := loadObject[ObjectT](filename)
	require.NoError(t, err)
	require.Equal(t, expected, got)
}

func TestGetServiceURL(t *testing.T) {
	got := GetServiceURL("envoy-gateway-system", "example-cluster.local")
	require.Equal(t, "grpc://envoy-ratelimit.envoy-gateway-system.svc.example-cluster.local:8081", got)
}
